<?php
/**
 * Created by PhpStorm.
 * User: wen
 * Date: 2021/7/10
 * Time: 7:28
 */

namespace WenRuns\Laravel\Admin;


use Closure;
use Encore\Admin\Form\Field;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Symfony\Component\HttpFoundation\Response;
use WenRuns\Laravel\Admin\Form\Builder;
use WenRuns\Laravel\Admin\Form\Field\ApiSelect;
use WenRuns\Laravel\Admin\Form\Field\CheckboxTree;
use WenRuns\Laravel\Admin\Form\Field\ExcelSheet;
use WenRuns\Laravel\Admin\Form\Field\InputSelect;
use WenRuns\Laravel\Admin\Form\Field\MultiCheckbox;
use WenRuns\Laravel\Admin\Form\Field\MultiList\MultiList;
use WenRuns\Laravel\Admin\Form\Field\Tabs;
use WenRuns\Laravel\Laravel;
use WenRuns\Laravel\Admin\Form\Field\Grid\Grid;

/**
 * Class Form
 * @package WenRuns\Laravel\Admin
 * @method MultiList        multiList($column, \Closure $closure, $label = '')
 * @method ApiSelect        apiSelect($column, $label = '')
 * @method CheckboxTree     checkboxTree($column, $label = '')
 * @method InputSelect      inputSelect($column, $label = '')
 * @method MultiCheckbox    multiCheckbox($column, $label = '')
 * @method Tabs             wenTab($title, \Closure $closure)
 * @method ExcelSheet       excelSheet($column, $label = '')
 * @method Grid             grid($column, \Closure $closure, $label = '')
 */
class Form extends \Encore\Admin\Form
{

    /**
     * @var array
     */
    protected $css = [];

    /**
     * @var array
     */
    protected $script = [];


    public function __construct($model, Closure $callback = null)
    {
        Laravel::loadJs('form/my.form.js');
        Laravel::loadCss('form/my.form.css');
        $this->model = $model;

        $this->builder = new Builder($this);

        $this->initLayout();

        if ($callback instanceof Closure) {
            $callback($this);
        }

        $this->isSoftDeletes = in_array(SoftDeletes::class, class_uses_deep($this->model), true);

        $this->callInitCallbacks();
    }

    /**
     * @param $css
     * @return $this|array
     */
    public function css($css = null)
    {
        if ($css === null) {
            return $this->css;
        }
        if (is_callable($css)) {
            $css = call_user_func($css, $this);
        }
        $type = 'style';
        $isFile = preg_match('/\/.*\.css($|\?.*$)/', $css);
        if ($isFile && is_file(public_path($css))) {
            $type = 'link';
        } else if ($isFile && is_file($css)) {
            $css = file_get_contents($css);
        }
        $this->css[] = [
            'content' => $css,
            'type' => $type,
        ];
        return $this;
    }

    /**
     * @param $script
     * @return $this|array
     */
    public function script($script = null)
    {
        if ($script === null) {
            return $this->script;
        }
        if (is_callable($script)) {
            $script = call_user_func($script, $this);
        }
        $type = 'script';
        $isFile = preg_match('/\/.*\.js($|\?.*$)/', $script);
        if ($isFile && is_file(public_path($script))) {
            $type = 'link';
        } else {
            if ($isFile && is_file($script)) {
                $script = file_get_contents($script);
            }
            $script = compressHtml($script);
        }
        $this->script[] = [
            'content' => $script,
            'type' => $type,
        ];
        return $this;
    }


    public function update($id, $data = null)
    {
        $data = ($data) ?: request()->all();

        $isEditable = $this->isEditable($data);

        if (($data = $this->handleColumnUpdates($id, $data)) instanceof Response) {
            return $data;
        }

        /* @var Model $this ->model */
        $builder = $this->model();

        if ($this->isSoftDeletes) {
            $builder = $builder->withTrashed();
        }

        $this->model = $builder->with($this->getRelations())->findOrFail($id);

        $this->setFieldOriginalValue();

        // Handle validation errors.
        if ($validationMessages = $this->validationMessages($data)) {
            if (!$isEditable) {
                return back()->withInput()->withErrors($validationMessages);
            }

            return response()->json(['errors' => Arr::dot($validationMessages->getMessages())], 422);
        }

        if (($response = $this->prepare($data)) instanceof Response) {
            return $response;
        }

        DB::transaction(function () {
            $updates = $this->prepareUpdate($this->updates);
            foreach ($updates as $column => $value) {
                /* @var Model $this ->model */
                $this->model->setAttribute($column, $value);
            }

            $this->model->save();
            $this->updateRelation($this->relations);
        });

        if (($result = $this->callSaved()) instanceof Response) {
            return $result;
        }

        if ($response = $this->ajaxResponse(trans('admin.update_succeeded'))) {
            return $response;
        }

        return $this->redirectAfterUpdate($id);
    }


    protected function prepareUpdate(array $updates, $oneToOneRelation = false): array
    {
        $prepared = [];

        /** @var Field $field */
        foreach ($this->fields() as $field) {
            $columns = $field->column();
            if (is_array($columns)) {
                $columns = array_diff($columns, $this->ignored);
            }
            // If column not in input array data, then continue.
            if (!Arr::has($updates, $columns)) {
                continue;
            }

            if ($this->isInvalidColumn($columns, $oneToOneRelation || $field->isJsonType)) {
                continue;
            }

            $value = $this->getDataByColumn($updates, $columns);

            $value = $field->prepare($value);

            if (is_array($columns)) {
                foreach ($columns as $name => $column) {
                    Arr::set($prepared, $column, $value[$name]);
                }
            } elseif (is_string($columns)) {
                Arr::set($prepared, $columns, $value);
            }
        }

        return $prepared;
    }

    protected function applayFieldDisplay()
    {
        $editable = [];

        /** @var Field $field */
        foreach ($this->fields() as $field) {
            if (!\request()->has($field->column())) {
                continue;
            }
            $columns = $field->column();
            if (is_array($columns)) {
                foreach ($columns as $column) {
                    $editable = $this->editableRefresh($editable, $field, $column);
                }
                continue;
            }
            $editable = $this->editableRefresh($editable, $field, $columns);
        }

        return $editable;
    }

    protected function editableRefresh($editable, $field, $column)
    {
        $newValue = $this->model->fresh()->getAttribute($column);
        if ($newValue instanceof Arrayable) {
            $newValue = $newValue->toArray();
        }

        if ($field instanceof Field\BelongsTo || $field instanceof Field\BelongsToMany) {
            $selectable = $field->getSelectable();

            if (method_exists($selectable, 'display')) {
                $display = $selectable::display();

                $editable[$column] = $display->call($this->model, $newValue);
            }
        }
        return $editable;
    }


    protected function prepareInsert($inserts): array
    {
        if ($this->isHasOneRelation($inserts)) {
            $inserts = Arr::dot($inserts);
        }

        foreach ($inserts as $column => $value) {
            if (($field = $this->getFieldByColumn($column)) === null) {
                unset($inserts[$column]);
                continue;
            }

            $inserts[$column] = $field->prepare($value);
        }

        $prepared = [];

        foreach ($inserts as $key => $value) {
            Arr::set($prepared, $key, $value);
        }

        return $prepared;
    }
}
